home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / quarkpy / mapmgr.py < prev    next >
Text File  |  2004-01-05  |  36KB  |  904 lines

  1. """   QuArK  -  Quake Army Knife
  2.  
  3. Map editor Layout managers.
  4. """
  5. #
  6. # Copyright (C) 1996-99 Armin Rigo
  7. # THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
  8. # FOUND IN FILE "COPYING.TXT"
  9. #
  10.  
  11. #$Header: /cvsroot/quark/runtime/quarkpy/mapmgr.py,v 1.12 2003/12/17 13:58:59 peter-b Exp $
  12.  
  13.  
  14.  
  15. #
  16. # This file defines the base class for Map Layout Managers.
  17. # This is an abstract class that must be overridden in plug-ins
  18. # (see e.g. mapclassiclayout.py). The instance of the current
  19. # Map Layout Manager is stored in the map editor's "layout"
  20. # attribute.
  21. #
  22. # Map Layouts mainly have the following attributes:
  23. #
  24. #  * editor  is the map editor
  25. #  * explorer  is the tree view screen control. To get the list of
  26. #              currently selected objects, use "explorer.sellist".
  27. #  * views  lists all currently opened map views
  28. #  * baseviews  lists only the main map views
  29. #
  30. # Map views display parameters are stored in "view.info", which is
  31. # a dictionnary. It must contain at least the key "type", which can
  32. # map to either "3D" for perspective view, or "2D", "XY", "XZ", ...
  33. # 2D views have attributes "scale", "angle", and so on. If you change
  34. # one of these attributes, you must call setprojmode to update the
  35. # display. See setprojmode for more details.
  36. #
  37. # Note about hints : All hints used by buttons, controls, etc, may
  38. # have a special syntax. Here are the main three possibilities :
  39. #   "some description"   -- the description appears both in the hint
  40. #                           panel (lower left) and when the mouse
  41. #                           stays on the control for a while
  42. #   "text1|text2"        -- text1 appears when the mouse stays on
  43. #                           the control for a while, and text2
  44. #                           appears in the hint panel
  45. #   "text1||text2"       -- text1 appears when the mouse stays on
  46. #                           the control for a while, and the hint
  47. #                           panel displays "Press F1 for help".
  48. #                           If the user then presses F1, he obtains
  49. #                           text2 in a pop-up "help snippet" window,
  50. #                           suitable for long explanations.
  51. #
  52.  
  53.  
  54. import math
  55. import quarkx
  56. import qtoolbar
  57. import qmenu
  58. from maputils import *
  59. import mapbtns
  60. import maphandles
  61. import maptools
  62. from qdictionnary import Strings
  63. from qbasemgr import BaseLayout
  64. from qbasemgr import MPPage
  65.  
  66.  
  67. SFTexts   = ["Easy", "Medium", "Hard", "Deathmatch", "Coop", "Single"]
  68. SFLetters = "emhdcs"
  69.  
  70.  
  71. class MapLayout(BaseLayout):
  72.     "An abstract base class for Map Editor screen layouts."
  73.  
  74.     MODE = SS_MAP
  75.     MAXAUTOZOOM = 1.0
  76.  
  77.     def clearrefs(self):
  78.         BaseLayout.clearrefs(self)
  79.         self.dataform = None
  80.         self.polyform = None
  81.         self.polyview = None
  82.         self.faceform = None
  83.         self.faceview = None
  84.         self.faceflags = None
  85.  
  86.  
  87.     def readtoolbars(self, config):
  88.         readtoolbars(maptools.toolbars, self, self.editor.form, config)
  89.  
  90.  
  91.  
  92.     def bs_dataform(self, panel):
  93.         ico_maped=ico_dict['ico_maped']
  94.         fp = panel.newpanel()
  95.         sfskills = (256,512,1024,2048)   # default
  96.         for q in quarkx.getqctxlist():
  97.             s = q["SFSkills"]
  98.             if type(s)==type(()):
  99.                 sfskills = s
  100.         mnu = []
  101.         for i in range(0, len(sfskills)):
  102.             item = qmenu.item(SFTexts[i], self.spawnflagsclick)
  103.             item.skill = int(sfskills[i])
  104.             mnu.append(item)
  105.         sfbtn = qtoolbar.menubutton(mnu, "skill levels||This button lets you control in which difficulty settings the entity should appear in the game. This lets you make a map harder by for example hiding some health boxes in the upper skill levels. You can also hide some entities in deatchmatch games, for example some doors, or a wall (this is the purpose of the func_wall entity).", ico_maped, 10)
  106.         sfbtn.caption = SFLetters[:len(sfskills)]
  107.         addspec = qtoolbar.button(self.plusminusclick, "insert an empty Specific/Arg pair", ico_maped, 11)
  108.         addspec.cmd = 0
  109.         deletespec = qtoolbar.button(self.plusminusclick, "delete a Specific/Arg pair", ico_maped, 12)
  110.         deletespec.cmd = 1
  111.         helpbtn = qtoolbar.button(self.helpbtnclick, "", ico_maped, 13)
  112.         helpbtn.local = 1
  113.         self.buttons.update({"help": helpbtn, "sf": sfbtn})
  114.         bb = fp.newtoppanel(ico_maped_y,0).newbtnpanel([sfbtn, qtoolbar.widegap, addspec, deletespec, qtoolbar.widegap, helpbtn])
  115.         bb.margins = (0,0)
  116.         df = fp.newdataform()
  117.         df.allowedit = 1
  118.         df.addremaining = 1
  119.         df.actionchanging = 512   # indexes in qdictionnary.Strings
  120.         df.actiondeleting = 553
  121.         df.actionrenaming = 566
  122.         df.editnames = "classname"
  123.         df.flags = DF_AUTOFOCUS
  124.         df.bluehint = 1
  125.         self.dataform = df
  126.         return fp
  127.  
  128.     def texflags(self, txt):
  129.         if self.editor.texflags:
  130.             return [qtoolbar.button(self.flagsclick, "flags for this "+txt, ico_dict['ico_maped'], 22)]
  131.         else:
  132.             return []    # Quake1, Hexen II
  133.  
  134.     def bs_polyform(self, panel):
  135.         ico_maped=ico_dict['ico_maped']
  136.         fp = panel.newpanel()
  137.         TexBtn = qtoolbar.button(mapbtns.texturebrowser, "choose texture", ico_maped, 0)
  138.         NegBtn = qtoolbar.button(self.neg1click, "negative poly||When a polyhedron is marked as negative, it behaves like a hole : every polyhedron in the same group as this one is 'digged' by the overlapping part.\n\nUsing 'Brush subtraction' in the 'Commands' menu is the same as marking the polyhedron negative, except that digging is not performed immediately. This helps keep the map clear.\n\nNegative polyhedrons appear in pink on the map.", ico_maped, 23)
  139.         self.buttons["negpoly"] = NegBtn
  140.         tp = fp.newtoppanel(124)
  141.         tp.newbottompanel(ico_maped_y,0).newbtnpanel([TexBtn, qtoolbar.widegap, NegBtn, qtoolbar.padright] + self.texflags("polyhedron"))
  142.         self.polyform = tp.newdataform()
  143.         self.polyform.header = 0
  144.         self.polyform.sep = -79
  145.         self.polyform.setdata([], quarkx.getqctxlist(':form', "Polyhedron")[-1])
  146.         self.polyform.onchange = self.polyformchange
  147.         self.polyview = fp.newmapview()
  148.         self.polyview.color = NOCOLOR
  149.         self.polyview.ondraw = self.polyviewdraw
  150.         self.polyview.onmouse = self.polyviewmouse
  151.         self.polyview.hint = "|click to select texture"
  152.         return fp
  153.  
  154.     def bs_faceform(self, panel):
  155.         ico_maped=ico_dict['ico_maped']
  156.         fp = panel.newpanel()
  157.         TexBtn = qtoolbar.button(mapbtns.texturebrowser, "choose texture", ico_maped, 1)
  158.         ts1Btn = qtoolbar.button(self.resettexscale, "reset 1:1 texture scale|resets 'scales' and 'angles'", ico_maped, 17)
  159.         ts1Btn.adjust = 0
  160.         ts2Btn = qtoolbar.button(self.resettexscale, "adjust texture to fit the face", ico_maped, 18)
  161.         ts2Btn.adjust = 1
  162.         ts3Btn = qtoolbar.button(self.resettexscale, "adjust texture on face but keep scaling to a minimum|adjust texture with minimum scaling", ico_maped, 24)
  163.         ts3Btn.adjust = 2
  164.         prevface = qtoolbar.button(self.nextface, "previous face of poly.", ico_dict['ico_mapedsm'], 0)
  165.         prevface.delta = -1
  166.         nextface = qtoolbar.button(self.nextface, "next face of poly.", ico_dict['ico_mapedsm'], 1)
  167.         nextface.delta = 1
  168.         #facezoombtn = qtoolbar.doublebutton(self.zoomface1click, getzoommenu, "choose zoom factor / zoom to 1:1 and back", ico_maped, 14)
  169.         #facezoombtn.caption = "zoom"
  170.         facezoombtn = qtoolbar.menubutton(getzoommenu, "choose zoom factor", ico_maped, 14)
  171.         facezoombtn.near = 1
  172.         self.buttons.update({"facezoom": facezoombtn, "prevf": prevface, "nextf": nextface})
  173.         tp = fp.newtoppanel(142)
  174.         btnp = tp.newbottompanel(ico_maped_y,0).newbtnpanel([prevface, nextface, facezoombtn, qtoolbar.smallgap, TexBtn, ts1Btn, ts2Btn, ts3Btn] + self.texflags("face"))
  175.         btnp.margins = (0,0)
  176.         self.faceform = tp.newdataform()
  177.         self.faceform.header = 0
  178.         self.faceform.sep = -79
  179.         self.faceform.setdata([], quarkx.getqctxlist(':form', "Face")[-1])
  180.         self.faceform.onchange = self.faceformchange
  181.         self.faceview = fp.newmapview()
  182.         facezoombtn.views = [self.faceview]
  183.         return fp
  184.  
  185.     def bs_bezierform(self, panel):
  186.         ico_maped=ico_dict['ico_maped']
  187.         fp = panel.newpanel()
  188.         bezierzoombtn = qtoolbar.menubutton(getzoommenu, "choose zoom factor", ico_maped, 14)
  189.         bezierzoombtn.near = 1
  190.         TexBtn = qtoolbar.button(mapbtns.texturebrowser, "choose texture", ico_maped, 1)
  191.         self.buttons["bezierzoom"] = bezierzoombtn
  192.         tp = fp.newtoppanel(70)
  193.         btnp = tp.newbottompanel(23,0).newbtnpanel([bezierzoombtn, qtoolbar.smallgap, TexBtn, qtoolbar.smallgap] + self.texflags("bezier patch"))
  194. #        btnp = tp.newbottompanel(23,0).newbtnpanel([bezierzoombtn, TexBtn] + self.texflags("bezier patch"))
  195.         btnp.margins = (0,0)
  196.         self.bezierform = tp.newdataform()
  197.         self.bezierform.header = 0
  198.         self.bezierform.sep = -79
  199.         self.bezierform.setdata([], quarkx.getqctxlist(':form', "Bezier")[-1])
  200.         self.bezierform.onchange = self.bezierformchange
  201.         self.bezierview = fp.newmapview()
  202.         # bezierzoombtn.views = [self.bezierview]
  203.         self.bezierview.color = NOCOLOR
  204.         bezierzoombtn.views = [self.bezierview]
  205.         return fp
  206.  
  207.     def bs_additionalpages(self, panel):
  208.         "Builds additional pages for the multi-pages panel."
  209.         thesepages = []
  210.         page1 = qtoolbar.button(self.filldataform, "Specifics/Args-view||Specifics/Args-view:\n\nThis view displays the general parameters for the selected object(s).\n\nSee the infobase for a more detailed description and use of this view display.", ico_objects, iiEntity, "Specifics/Args-view", infobaselink='intro.mapeditor.dataforms.html#specsargsview')
  211.         page1.pc = [self.bs_dataform(panel)]
  212.         thesepages.append(page1)
  213.         page2 = qtoolbar.button(self.fillpolyform, "Polyhedron-view||Polyhedron-view:\n\nThis display shows the parameters about the selected polyhedron(s).\n\nSee the infobase for a more detailed description and use of this view display.", ico_objects, iiPolyhedron, "Polyhedron-view", infobaselink='intro.mapeditor.dataforms.html#polyhedronview')
  214.         page2.pc = [self.bs_polyform(panel)]
  215.         thesepages.append(page2)
  216.         page3 = qtoolbar.button(self.fillfaceform, "Face-view||Face-view:\n\nThis display shows the parameters about the selected face(s).\n\nSee the infobase for a more detailed description and use of this view display.", ico_objects, iiFace, "Face-view", infobaselink='intro.mapeditor.dataforms.html#faceview')
  217.         page3.pc = [self.bs_faceform(panel)]
  218.         page3.needangle = 1
  219.         thesepages.append(page3)
  220.         # only show the bezier-page, if the game supports bezierpatches
  221.         beziersupport = quarkx.setupsubset()["BezierPatchSupport"]
  222.         if (beziersupport is not None) and (beziersupport == "1"):
  223.             page4 = qtoolbar.button(self.fillbezierform, Strings[-459], ico_objects, iiBezier)#, Strings[-409])
  224.             page4.pc = [self.bs_bezierform(panel)]
  225.             thesepages.append(page4)
  226.         return thesepages, mppages
  227.  
  228.     def bs_userobjects(self, panel):
  229.         "A panel with user-defined map objects."
  230.         MapUserDataPanel(panel,
  231.           "Drop your most commonly used prefabs and entities to this panel||This panel is a good place to put 'prefabs', that is, nice entities, polyhedrons, or whole groups.\n\nYou add prefabs to the panel by dragging them from the tree view; they become buttons on which you can click to re-insert them in your maps.\n\nApart from the first line of buttons, you can reorder them by dragging them around with the mouse, and remove them by dragging them to the 'trash' button below.|intro.mapeditor.userdata.html",
  232.           "MapObjPanel.qrk", "UserData %s.qrk" % self.editor.gamecfg)
  233.  
  234.  
  235.     def actionmpp(self):
  236.         "Automatically switch the multi-pages-panel for the current selection."
  237.         if (self.mpp.n<4) and not (self.mpp.lock.state & qtoolbar.selected):
  238.             fs = self.explorer.focussel
  239.             if fs is None:
  240.                 self.mpp.viewpage(0)
  241.             elif fs.type == ':e':
  242.                 self.mpp.viewpage(1)
  243.             elif fs.type == ':p':
  244.                 self.mpp.viewpage(2)
  245.             elif fs.type == ':f':
  246.                 self.mpp.viewpage(3)
  247.  
  248.     def filldataform(self, reserved):
  249.         import mapentities
  250.         sl = self.explorer.sellist
  251.         formobj = mapentities.LoadEntityForm(sl)
  252.         self.dataform.setdata(sl, formobj)
  253.         help = ((formobj is not None) and formobj["Help"]) or ""
  254.         if help:
  255.             help = "?" + help   # this trick displays a blue hint
  256.         self.buttons["help"].hint = help + "||This button gives you the description of the selected entity, and how to use it.\n\nYou are given help in two manners : by simply moving the mouse over the button, a 'hint' text appears with the description; if you click the button, you are sent to an HTML document about the entity, if available, or you are shown the same text as previously, if nothing more is available.\n\nNote that there is currently not a lot of info available as HTML documents."
  257.         sfbtn = self.buttons["sf"]
  258.         if sl:
  259.             icon = mapentities.EntityIconSel(sl[0])
  260.             for s in sl[1:]:
  261.                 icon2 = mapentities.EntityIconSel(s)
  262.                 if not (icon is icon2):
  263.                     icon = ico_objects[1][iiEntity]
  264.                     break
  265.             sfbtn.state = 0
  266.             cap = ""
  267.             for i in range(0, len(sfbtn.menu)):
  268.                 m = sfbtn.menu[i]
  269.                 if self.dataform.bitspec("spawnflags", m.skill):
  270.                     m.state = 0
  271.                 else:
  272.                     m.state = qmenu.checked
  273.                     cap = cap + SFLetters[i]
  274.             if not cap:
  275.                 cap = "----"
  276.         else:
  277.             sfbtn.state = qtoolbar.disabled
  278.             cap = SFLetters[:len(sfbtn.menu)]
  279.             icon = ico_objects[1][iiEntity]
  280.         sfbtn.caption = cap
  281.         btnlist = self.mpp.btnpanel.buttons
  282.         if not (btnlist[1].icons[3] is icon):
  283.             l = list(btnlist[1].icons)
  284.             l[3] = icon
  285.             l[4] = icon
  286.             btnlist[1].icons = tuple(l)
  287.             self.mpp.btnpanel.buttons = btnlist
  288.         quarkx.update(self.editor.form)
  289.  
  290.     def getpolylists(self):
  291.         slist = self.explorer.sellist
  292.         for s in slist[:]:
  293.             if s.type == ':f':
  294.                 slist.remove(s)
  295.                 slist = slist + s.faceof
  296.         plist = []
  297.         for s in slist:
  298.             for p in s.findallsubitems("", ':p'):   # find all polyhedrons
  299.                 if not (p in plist):
  300.                     plist.append(p)
  301.         return plist
  302.  
  303.     def fillpolyform(self, reserved):
  304.         self.polyview.invalidate(1)
  305.         plist = self.getpolylists()
  306.  
  307.         NegBtn = self.buttons["negpoly"]
  308.         if len(plist)==0:
  309.             ns = qtoolbar.disabled
  310.         else:
  311.             for p in plist:
  312.                 if p["neg"]:
  313.                     ns = qtoolbar.selected
  314.                     break
  315.             else:
  316.                 ns = 0
  317.         NegBtn.state = ns
  318.         quarkx.update(self.editor.form)
  319.  
  320.         q = quarkx.newobj(':')   # internal object
  321.         if len(plist)==0:
  322.             cap = Strings[145]
  323.         elif len(plist)==1:
  324.             cap = plist[0].error
  325.             if cap:
  326.                 cap = cap.capitalize()
  327.             else:
  328.                 cap = Strings[142]
  329.         else:
  330.             cap = Strings[143] % len(plist)
  331.         q["header"] = cap
  332.         cntf, cnti = 0, 0
  333.         for p in plist:
  334.             cnti = cnti + p.rebuildall()[1]
  335.             cntf = cntf + len(p.faces)
  336.         if cnti:
  337.             cap = Strings[141] % (cntf, cnti)
  338.         else:
  339.             cap = Strings[140] % cntf
  340.         q["faces"] = cap
  341.         if len(plist)==1:
  342.             cap = plist[0].origin
  343.             if cap is not None:
  344.                 q["center"] = cap.tuple
  345.         texlist = quarkx.texturesof(plist)
  346.         texhint = "TEX?"
  347.         for tex in texlist:
  348.             texhint = texhint + tex + ";"
  349.         if len(texlist)<=1:
  350.             if len(texlist):
  351.                 cap = texlist[0]
  352.             else:
  353.                 cap = ""
  354.             texlist = quarkx.texturesof([self.editor.Root])  # all textures in the map
  355.         else:
  356.             cap = Strings[179] % len(texlist)
  357.         q["texture"] = cap
  358.         q["oldtex"] = cap
  359.         q["texture$Items"] = quarkx.list2lines(texlist)
  360.         q["texture$Hint"] = texhint
  361.         if len(plist):
  362.             test = plist[0].parent
  363.             cap = test.shortname
  364.             for p in plist:
  365.                 test2 = p.parent
  366.                 if not (test2 is test):
  367.                     cap = Strings[144]
  368.                     break
  369.             q["ownedby"] = cap
  370.         self.polyform.setdata(q, self.polyform.form)
  371.  
  372.     def polyformchange(self, src):
  373.         plist = self.getpolylists()
  374.         undo = quarkx.action()
  375.  
  376.         q = src.linkedobjects[0]
  377.         ncenter = q["center"]
  378.         if ncenter is not None:
  379.             ncenter = quarkx.vect(ncenter)   # tuple->vect
  380.             for p in plist:
  381.                 org = p.origin
  382.                 if (org is not None) and (ncenter-org):
  383.                     new = p.copy()
  384.                     new.translate(ncenter-org)
  385.                     plist[plist.index(p)] = new
  386.                     undo.exchange(p, new)
  387.         ntex = q["texture"]
  388.         applycount = 0
  389.         if (ntex is not None) and (ntex!="") and (ntex!=q["oldtex"]):
  390.             for p in plist:
  391.                 # this implicitely uses the 'undo' variable
  392.                 applycount = applycount + p.replacetex("", ntex, 1)
  393.         if applycount:
  394.             if applycount>1:
  395.                 txt = Strings[547] % applycount
  396.             else:
  397.                 txt = Strings[546]
  398.         else:
  399.             txt = Strings[515]
  400.         self.editor.ok(undo, txt)
  401.  
  402.  
  403.     def polyviewdraw(self, view):
  404.         texlist = quarkx.texturesof(self.explorer.sellist)
  405.         if len(texlist)==1:
  406.             tex = quarkx.loadtexture(texlist[0], self.editor.TexSource)
  407.             if not (tex is None):
  408.                 view.canvas().painttexture(tex, (0,0)+view.clientarea, -1)
  409.                 return
  410.         w,h = view.clientarea
  411.         cv = view.canvas()
  412.         cv.penstyle = PS_CLEAR
  413.         cv.brushcolor = GRAY
  414.         cv.rectangle(0,0,w,h)
  415.  
  416.     def polyviewmouse(self, view, x, y, flags, handle):
  417.         if flags&MB_CLICKED:
  418.             quarkx.clickform = view.owner
  419.             mapbtns.texturebrowser()
  420.  
  421.  
  422.     def getfacelists(self):
  423.         "Find all selected faces."
  424.         flist = []
  425.         for s in self.explorer.sellist:
  426.             for f in s.findallsubitems("", ':f'):   # find all faces
  427.                 if not (f in flist):
  428.                     flist.append(f)
  429.         return flist
  430.  
  431.     def getbezierlists(self):
  432.         "Find all selected BΘzier patches."
  433.         blist = []
  434.         b2list = []
  435.         for s in self.explorer.sellist:
  436.             for b in s.findallsubitems("",':b2'):
  437.                 if not (b in b2list):
  438.                     b2list.append(b)
  439.         return blist+b2list
  440.  
  441.  
  442.     def zoomface1click(self, facezoombtn):
  443.         flist = self.getfacelists()
  444.         if len(flist)!=1: return
  445.         if not maphandles.singlefaceautozoom(self.faceview, flist[0]):
  446.             self.faceview.info["scale"] = 1.0
  447.             maphandles.singlefacezoom(self.faceview)
  448.  
  449.     def resettexscale(self, btn):
  450.         mapbtns.resettexscale(self.editor, self.getfacelists(), btn.adjust)
  451.  
  452.     def nextface(self, btn):
  453.         "Implements the previous and next face buttons."
  454.         flist = self.getfacelists()
  455.         if len(flist)!=1:
  456.             return
  457.         face = flist[0]
  458.         i = face.parent.subitems.index(face)   # face.parent in this case is the polyhedron containing this case
  459.         self.explorer.uniquesel = face.parent.subitem(i + btn.delta)
  460.  
  461.  
  462.     def fillfaceform(self, reserved):
  463.         flist = self.getfacelists()
  464.         q = quarkx.newobj(':')   # internal object
  465.         self.faceview.handles = []
  466.         self.faceview.ondraw = None
  467.         self.faceview.onmouse = self.polyviewmouse
  468.         self.faceview.color = NOCOLOR
  469.         self.faceview.invalidate(1)
  470.         facezoombtn = self.buttons["facezoom"]
  471.         facezoombtn.state = qtoolbar.disabled
  472.         if len(flist)==0:
  473.             cap = Strings[129]
  474.         elif len(flist)==1:
  475.             if maphandles.viewsingleface(self.editor, self.faceview, flist[0]):
  476.                 facezoombtn.state = 0
  477.             cap = Strings[134]
  478.         else:
  479.             cap = Strings[135] % len(flist)
  480.         q["header"] = cap
  481.         if self.faceview.ondraw is None:
  482.             self.faceview.ondraw = self.polyviewdraw
  483.             self.faceview.hint = "|click to select texture"
  484.         else:
  485.             self.faceview.hint = ""
  486.         prevnext = 3
  487.         if len(flist)==1:
  488.             faceof = flist[0].faceof
  489.             if not faceof:       # empty -> broken face
  490.                 cap = ""
  491.             elif faceof[0] is flist[0]:  # face of itself ==> not used by anything else
  492.                 cap = Strings[133]
  493.             elif flist[0].parent.type == ':p':   # face of a polyhedron ==> not shared
  494.                 cap = Strings[130]
  495.                 items = flist[0].parent.subitems
  496.                 i = items.index(flist[0])
  497.                 if i: prevnext = 2
  498.                 if i<len(items)-1: prevnext = prevnext - 2
  499.             else:
  500.                 cap = Strings[131] % len(faceof)
  501.             q["sharedby"] = cap
  502.         self.buttons["prevf"].state = (prevnext&1) and qtoolbar.disabled
  503.         self.buttons["nextf"].state = (prevnext&2) and qtoolbar.disabled
  504.         texlist = quarkx.texturesof(flist)
  505.         texhint = "TEX?"
  506.         for tex in texlist:
  507.             texhint = texhint + tex + ";"
  508.         if len(texlist)<=1:
  509.             if len(texlist):
  510.                 cap = texlist[0]
  511.             else:
  512.                 cap = ""
  513.             texlist = quarkx.texturesof([self.editor.Root])  # all textures in the map
  514.         else:
  515.             cap = Strings[179] % len(texlist)
  516.         q["texture"] = cap
  517.         q["oldtex"] = cap
  518.         q["texture$Items"] = quarkx.list2lines(texlist)
  519.         q["texture$Hint"] = texhint
  520.         multiple = "?"
  521.         org = sc = sa = None
  522.         for f in flist:
  523.             tp = f.threepoints(1)
  524.             if tp is not None:
  525.                 if org is None:
  526.                     org = tp[0]
  527.                 elif (org is not multiple) and (org-tp[0]):
  528.                     org = multiple
  529.                 tp1, tp2 = tp[1]-tp[0], tp[2]-tp[0]
  530.                 nsc = (abs(tp1)/128, abs(tp2)/128)
  531.                 if sc is None:
  532.                     sc = nsc
  533.                 elif (sc is not multiple) and (abs(sc[0]-nsc[0])+abs(sc[1]-nsc[1]) > epsilon):
  534.                     sc = multiple
  535.                 n = f.normal
  536.                 if not n: continue
  537.                 v1 = orthogonalvect(n, self.views[0])
  538.                 v2 = n^v1
  539.                 nsa = (math.atan2(v2*tp1, v1*tp1), math.atan2(v2*tp2, v1*tp2))
  540.                 if sa is None:
  541.                     sa = nsa
  542.                 elif (sa is not multiple) and ((abs(sa[0]-nsa[0]) > epsilon) or (abs(sa[1]-nsa[1]) > epsilon)):
  543.                     sa = multiple
  544.  
  545.         if (org is not None) and (org is not multiple):
  546.             q["origin"] = org.tuple
  547.         if (sc is not None) and (sc is not multiple):
  548.             q["scales"] = sc
  549.         if (sa is not None) and (sa is not multiple):
  550.             q["angles"] = (sa[0] * rad2deg, sa[1] * rad2deg)
  551.  
  552.         self.faceform.setdata(q, self.faceform.form)
  553.         quarkx.update(self.editor.form)
  554.  
  555.  
  556.     def faceformchange(self, src):
  557.         flist = self.getfacelists()
  558.         undo = quarkx.action()
  559.  
  560.         q = src.linkedobjects[0]
  561.         norg = q["origin"]
  562.         nsc = q["scales"]
  563.         nsa = q["angles"]
  564.         if norg is not None:
  565.             norg = quarkx.vect(norg)   # tuple->vect
  566.         if nsc is not None:
  567.             if abs(nsc[0])<epsilon or abs(nsc[1])<epsilon:
  568.                 quarkx.msgbox("Texture scale cannot be zero.", MT_ERROR, MB_OK)
  569.                 return
  570.         if nsa is not None:
  571.             ang1, ang2 = nsa
  572.             diff = (ang1-ang2)%180
  573.             if diff<5 or diff>175:
  574.                 quarkx.msgbox("The two texture angles must be more different.", MT_ERROR, MB_OK)
  575.                 return
  576.             ang1 = ang1 * deg2rad
  577.             ang2 = ang2 * deg2rad
  578.         for f in flist:
  579.             tp = f.threepoints(1)
  580.             if tp is not None:
  581.                 ntp = tp
  582.                 tp1, tp2 = tp[1]-tp[0], tp[2]-tp[0]
  583.                 if norg is not None:
  584.                     delta = norg-tp[0]
  585.                     if delta:
  586.                         ntp = tuple(map(lambda v, delta=delta: v+delta, ntp))
  587.                 if nsa is not None:
  588.                     n = f.normal
  589.                     v1 = orthogonalvect(n, self.views[0])
  590.                     v2 = n^v1
  591.                     sa1, sa2 = math.atan2(v2*tp1, v1*tp1), math.atan2(v2*tp2, v1*tp2)
  592.                     newangles = (abs(sa1-ang1)>epsilon) or (abs(sa2-ang2)>epsilon)
  593.                 else:
  594.                     newangles = 0
  595.                 if newangles or (nsc is not None):
  596.                     sc = (abs(tp1)/128, abs(tp2)/128)
  597.                     if newangles:
  598.                         if nsc is not None:
  599.                             sc = nsc
  600.                         ntp = (ntp[0],
  601.                                ntp[0] + (v1*math.cos(ang1) + v2*math.sin(ang1)) * sc[0] * 128,
  602.                                ntp[0] + (v1*math.cos(ang2) + v2*math.sin(ang2)) * sc[0] * 128)
  603.                     elif abs(sc[0]-nsc[0])+abs(sc[1]-nsc[1]) > epsilon:
  604.                         ntp = (ntp[0],
  605.                                ntp[0] + (ntp[1]-ntp[0])*(nsc[0]/sc[0]),
  606.                                ntp[0] + (ntp[2]-ntp[0])*(nsc[1]/sc[1]))
  607.                 if ntp is not tp:
  608.                     new = f.copy()
  609.                     new.setthreepoints(ntp, 3)
  610.                     flist[flist.index(f)] = new
  611.                     undo.exchange(f, new)
  612.         ntex = q["texture"]
  613.         applycount = 0
  614.         if (ntex is not None) and (ntex!="") and (ntex!=q["oldtex"]):
  615.             for f in flist:
  616.                 # this implicitely uses the 'undo' variable
  617.                 applycount = applycount + f.replacetex("", ntex, 1)
  618.         if applycount:
  619.             if applycount>1:
  620.                 txt = Strings[547] % applycount
  621.             else:
  622.                 txt = Strings[546]
  623.         else:
  624.             txt = Strings[597]
  625.         self.editor.ok(undo, txt)
  626.  
  627.  
  628.     def fillbezierform(self, reserved):
  629.         self.bezierview.handles = []
  630.         self.bezierview.ondraw = None
  631.         self.bezierview.onmouse = self.polyviewmouse
  632.         self.bezierview.invalidate(1)
  633.         self.bezierview.info = None
  634.         bezierzoombtn = self.buttons["bezierzoom"]
  635.         bezierzoombtn.state = qtoolbar.disabled
  636.         blist = self.getbezierlists()
  637.         # quarkx.update(self.editor.form)  -- done below
  638.  
  639.         q = quarkx.newobj(':')   # internal object
  640.         if len(blist)==0:
  641.             cap = Strings[128]
  642.         elif len(blist)==1:
  643.             if maphandles.viewsinglebezier(self.bezierview, self, blist[0]):
  644.                 bezierzoombtn.state = 0
  645.             cap = Strings[126]
  646.         else:
  647.             cap = Strings[127] % len(blist)
  648.         if self.bezierview.ondraw is None:
  649.             self.bezierview.ondraw = self.polyviewdraw
  650.             self.bezierview.hint = "|click to select texture"
  651.         else:
  652.             self.bezierview.hint = ""
  653.         q["header"] = cap
  654.         texlist = quarkx.texturesof(blist)
  655.         texhint = "TEX?"
  656.         for tex in texlist:
  657.             texhint = texhint + tex + ";"
  658.         if len(texlist)<=1:
  659.             if len(texlist):
  660.                 cap = texlist[0]
  661.             else:
  662.                 cap = ""
  663.             texlist = quarkx.texturesof([self.editor.Root])  # all textures in the map
  664.         else:
  665.             cap = Strings[179] % len(texlist)
  666.         q["texture"] = cap
  667.         q["oldtex"] = cap
  668.         q["texture$Items"] = quarkx.list2lines(texlist)
  669.         q["texture$Hint"] = texhint
  670.  
  671.         self.bezierform.setdata(q, self.bezierform.form)
  672.         quarkx.update(self.editor.form)
  673.  
  674.  
  675.     def bezierformchange(self, src):
  676.         blist = self.getbezierlists()
  677.         undo = quarkx.action()
  678.  
  679.         q = src.linkedobjects[0]
  680.         ntex = q["texture"]
  681.         if ntex is None: ntex = ""
  682.         applycount = 0
  683.         if ntex!=q["oldtex"]:
  684.             for b in blist:
  685.                 # this implicitely uses the 'undo' variable
  686.                 applycount = applycount + b.replacetex(None, ntex, 1)
  687.         if applycount:
  688.             if applycount>1:
  689.                 txt = Strings[547] % applycount
  690.             else:
  691.                 txt = Strings[546]
  692.         else:
  693.             txt = Strings[628]
  694.         self.editor.ok(undo, txt)
  695.  
  696.  
  697.     def spawnflagsclick(self, m):
  698.         self.dataform.togglebitspec("spawnflags", m.skill)
  699.  
  700.     def plusminusclick(self, m):
  701.         self.dataform.useraction(m.cmd)
  702.  
  703.     def helpbtnclick(self, m):
  704.         import mapentities
  705.         sl = self.explorer.sellist
  706.         formobj = mapentities.LoadEntityForm(sl)
  707.         if formobj is not None:
  708.             if formobj["HTML"]:
  709.                 formobj = formobj["HTML"]
  710.             elif formobj["Help"] and hasattr(m, "local"):
  711.                 quarkx.helppopup(formobj["Help"])
  712.                 return
  713.             else:
  714.                 formobj = None
  715.         htmldoc(formobj)
  716.  
  717.     def neg1click(self, m):
  718.         plist = self.getpolylists()
  719.         if m.state & qtoolbar.selected:
  720.             ns = None
  721.         else:
  722.             ns = "1"
  723.         undo = quarkx.action()
  724.         for p in plist:
  725.             if p["neg"] != ns:
  726.                 new = p.copy()
  727.                 new["neg"] = ns
  728.                 undo.exchange(p, new)
  729.         self.editor.ok(undo, Strings[621])
  730.  
  731.     def flagsclick(self, m):
  732.         ff = self.faceflags
  733.         form = None
  734.         if ff is None:
  735.             if mapeditor() is not self.editor: return
  736.             flist = quarkx.getqctxlist(":form", "TextureFlags")
  737.             if not len(flist):
  738.                 raise "TextureFlags form not found"
  739.             form = flist[-1]
  740.             ff = quarkx.clickform.newfloating(0, "Face Flags")
  741.             x1,y1,x2,y2 = quarkx.screenrect()
  742.             ff.windowrect = (x2-200, y1+100, x2-20, y2-20) #Decker 2002-04-07: Make the window bigger, as there are so many faceflags for Q2/SOF/SIN/KP!
  743.             ff.onclose = self.faceflagsclose
  744.             ff.begincolor = GREEN
  745.             ff.endcolor = OLIVE
  746.             df = ff.mainpanel.newdataform()
  747.             df.actionchanging = 596
  748.             df.header = 0
  749.             df.sep = 0.39
  750.             df.flags = DF_AUTOFOCUS | DF_LOCAL
  751.             self.faceflags = ff
  752.         self.loadfaceflags(form)
  753.         ff.show()
  754.  
  755.     def faceflagsclose(self, sender):
  756.         self.faceflags = None
  757.  
  758.     def loadtfflist(self, form=None):
  759.         flist = self.getfacelists()
  760.         txdict = {}
  761.         texsrc = self.editor.TexSource
  762.         #
  763.         # Make a dict of texobjs indexed by texnames
  764.         #
  765.         for tex in quarkx.texturesof(flist):
  766.             texobj = quarkx.loadtexture(tex, texsrc)
  767.             if texobj is not None:
  768.                 try:
  769.                     texobj = texobj.disktexture
  770.                 except quarkx.error:
  771.                     continue
  772.             txdict[tex] = texobj
  773.         #
  774.         # Handle the default values, is this really worth it, tiglari wonders
  775.         #
  776.         # FIXME(?): maybe this should be restricted to games
  777.         #   where defaults for the specifics are useful, or to
  778.         #   specifics whose name begins with _esp_.
  779.         #
  780.         #  Maybe there's a way to do all this in the Delphi
  781.         #
  782.         #  a specific is represented as a subitem of the form whose
  783.         #    shortname is the name of the specific as normally understood,
  784.         #    with the other stuff as specifics thereof.
  785.         #
  786.         # For each specific in the form, if it has a "Default", and if
  787.         #   the faces have different values for that specific, and then
  788.         #   for each texobj that lacks a value for the specific, replace
  789.         #   it with a copy that has the default (whew).  All this to
  790.         #   get the little question marks for disagreeing specifics in
  791.         #   the texture-flags window.
  792.         #
  793.         if form is not None and len(flist)>1: # no clashes if only one face
  794.             copied=0
  795.             def getspecval(spec, f, texobj):
  796.                 val = f[spec]
  797.                 if val is None:
  798.                     val = texobj[spec]
  799.                 return val
  800.  
  801.             for specific in form.subitems:
  802.                 if specific["Default"] is not None:
  803.                     sname = specific.shortname
  804.                     value = getspecval(sname, flist[0], txdict[flist[0].texturename])
  805.                     for i in range(1, len(flist)):
  806.                         f = flist[i]
  807.                         value2 = getspecval(sname, f, txdict[f.texturename])
  808.                         if value!=value2:
  809.                             for texobj in txdict.values():
  810.                                 if not copied:
  811.                                     texobj=texobj.copy()  # copy them all, God will recognize his own
  812.                                     txdict[f.texturename] = texobj
  813.                                     copied=1
  814.                                 if texobj[sname] is None:
  815.                                     texobj[sname]=specific["Default"]
  816.         #
  817.         # Build the pairs list
  818.         #
  819.         for i in range(0, len(flist)):
  820.             f = flist[i]
  821.             try:
  822.                 texobj = txdict[f.texturename]
  823.             except KeyError:
  824.                 continue
  825.             flist[i] = (f, texobj)
  826.         return flist
  827.  
  828.  
  829.     def loadfaceflags(self, form=None):
  830.         ff = self.faceflags
  831.         df = ff.mainpanel.controls()[0]
  832.         if form is None:
  833.             form = df.form
  834.         df.setdata(self.loadtfflist(form), form)
  835.  
  836.  
  837.     def selchange(self):
  838.         if self.faceflags is not None:
  839.             self.loadfaceflags()
  840.         self.mpp.resetpage()
  841.  
  842.  
  843.     def NewItem1Click(self, m):
  844.         quarkx.opentoolbox("New map items...")
  845.  
  846.     def cameramoved(self, view):
  847.         self.update3Dviews()
  848.         self.editor.oldcamerapos = view.cameraposition
  849.  
  850.  
  851.  
  852. #
  853. # List of all screen layouts
  854. # (the first one is the default one in case no other one is configured)
  855. # This list must be filled by plug-ins !
  856. #
  857. LayoutsList = []
  858.  
  859.  
  860. #
  861. # List of additionnal pages of the Multi-Pages-Panel
  862. # This list can be filled by plug-ins. (see mappage3d.py)
  863. #
  864. mppages = []
  865.  
  866. # ----------- REVISION HISTORY ------------
  867. #
  868. #
  869. #$Log: mapmgr.py,v $
  870. #Revision 1.12  2003/12/17 13:58:59  peter-b
  871. #- Rewrote defines for setting Python version
  872. #- Removed back-compatibility with Python 1.5
  873. #- Removed reliance on external string library from Python scripts
  874. #
  875. #Revision 1.11  2003/07/07 07:18:31  cdunde
  876. #To correct caption exclusion error and hint display
  877. #
  878. #Revision 1.10  2003/03/17 01:51:13  cdunde
  879. #Update hints and add infobase links where needed
  880. #
  881. #Revision 1.9  2002/05/05 10:19:59  tiglari
  882. #Support for texture flag defaults in layout.loadtfflist (a long run for a shortish slide)
  883. #
  884. #Revision 1.8  2002/04/07 12:46:31  decker_dk
  885. #Made the texture/face-flags window bigger.
  886. #
  887. #Revision 1.7  2001/10/22 10:24:32  tiglari
  888. #live pointer hunt, revise icon loading
  889. #
  890. #Revision 1.6  2001/03/01 19:14:40  decker_dk
  891. #changed bs_additionalpages() so it checks 'BezierPatchSupport' for the bezier-page.
  892. #
  893. #Revision 1.5  2001/02/25 11:22:51  tiglari
  894. #bezier page support, transplanted with permission from CryEd (CryTek)
  895. #
  896. #Revision 1.3  2001/01/02 19:29:51  decker_dk
  897. #Small changes in hint-descriptions
  898. #
  899. #Revision 1.2  2000/06/02 16:00:22  alexander
  900. #added cvs headers
  901. #
  902. #
  903. #
  904.